package model;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.geom.Point;
import flash.Lib;
#if flash
import flash.net.FileFilter;
import flash.net.FileReference;
#end
import graphic.RenderObject;
import graphic.RessourceManager;
import model.ModelUpdateType;
import phx.Body;
import phx.Polygon;
import phx.Shape;
import phx.Vector;
import view.PropertyPanel;
import graphic.Scene;
import haxe.xml.Fast;
import view.events.LevelEditorViewOtherEvent;

/**
 * @author Damien Mabin
 */
class LevelEditorModel extends EventDispatcher
{
	private var m_Scene 					: Scene;
	
	private var m_RenderObjectSelection		: Array<RenderObject>;
	private var m_PhysicPointSelection		: Array<Vector>;
	
	// Contain all the render object for which we can edit the physic shape
	private var m_EditableRenderObject		: RenderObject;
	
	private var m_TrickyFR 					: FileReference;
	
	public function new() 
	{
		super();
		
		m_EditableRenderObject = null;
		m_RenderObjectSelection = new Array<RenderObject>();
		m_PhysicPointSelection = new Array<Vector>();
		m_Scene = new Scene();
		m_Scene.m_PlayPhysic = false;
	}
	
	public function VUE_GetRenderObjects():Array<RenderObject>
	{
		return m_Scene.GetRenderObjects();
	}
	
	public function VUE_GetSelection<T>(_Class:Class<T>):Array<T>
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				return cast m_RenderObjectSelection;
			}
			case cast Vector :
			{
				return cast m_PhysicPointSelection;
			}
			default : 
			{
				return null;
			}
		}
	}
	
	public function CTRL_GetElementProperty<T>(_Class:Class<T>, _Elt:T, _PropertyType:E_PropertyType):String
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				var ro : RenderObject = cast _Elt;
				switch(_PropertyType)
				{
					case INSTANCE_NAME :
					{
						return ro.name;
					}
					case X_POSITION :
					{
						return "" + ro.GetPosition().x;
					}
					case Y_POSITION :
					{
						return "" + ro.GetPosition().y;
					}
					case X_SCALE :
					{
						return "" + ro.scaleX;
					}
					case Y_SCALE :
					{
						return "" + ro.scaleY;
					}
					case ROTATION :
					{
						return "" + ro.rotation;
					}
					case STATIC :
					{
						return "" + ro.GetPhysicalBody().isStatic;
					}
				}
			}
		}
		return "";
	}
	
	public function CTRL_ChangePhysicProperty(_PropertyType:E_PhysicPropertyType, _Value:String):Void
	{
		switch(_PropertyType)
		{
			case ADD_PHYSIC_POINT :
			{
				if (m_PhysicPointSelection.length == 1)
				{
					for (physShape in m_EditableRenderObject.GetPhysicalBody().shapes)
					{
						switch(physShape.type)
						{
							case Shape.POLYGON :
							{
								var ptIte : Vector = physShape.polygon.verts;
								var index : Int = 0;
								while (ptIte != null)
								{
									if (ptIte.x == m_PhysicPointSelection[0].x && ptIte.y == m_PhysicPointSelection[0].y)
									{
										// We add the point here !
										trace("TODO : add physic point in the model shape !");
										var physPoints : Array<Vector> = new Array<Vector>();
										var ptIte1 : Vector = physShape.polygon.verts;
										while (ptIte1 != null)
										{
											physPoints.push(ptIte1);
											ptIte1 = ptIte1.next;
										}
										// We insert just after the index of the point, the new point
										var newPt : Vector = new Vector(0, 0);
										physPoints.insert(index, new Vector(0, 0));
										// We remove from the body the current shape, and we add the new one :
										m_EditableRenderObject.GetPhysicalBody().removeShape(physShape);
										m_EditableRenderObject.GetPhysicalBody().addShape(new Polygon(physPoints, new Vector(0, 0)));
										this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.ADD_PHYSIC_POINT(m_PhysicPointSelection[0], newPt)));
										return;
									}
									ptIte = ptIte.next;
									index++;
								}
							}
						}
					}
				}
			}
		}
	}
	
	public function CTRL_SetElementProperty<T>(_Class:Class<T>, _Elt:T, _PropertyType:E_PropertyType, _PropertyValue:String):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				var ro : RenderObject = cast _Elt;
				switch(_PropertyType)
				{
					case INSTANCE_NAME :
					{
						ro.name = _PropertyValue;
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case X_POSITION :
					{
						var xValue : Float = Std.parseFloat(_PropertyValue);
						ro.x = xValue;
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case Y_POSITION :
					{
						var yValue : Float = Std.parseFloat(_PropertyValue);
						ro.y = yValue;
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case X_SCALE :
					{
						var scaleX : Float = Std.parseFloat(_PropertyValue);
						ro.scaleX = scaleX;
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case Y_SCALE :
					{
						var scaleY : Float = Std.parseFloat(_PropertyValue);
						ro.scaleY = scaleY;
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case ROTATION :
					{
						var rotation : Float = Std.parseFloat(_PropertyValue);
						ro.rotation = rotation;
						if (ro.GetPhysicalBody() != null)
						{
							ro.GetPhysicalBody().setAngle(Math.PI * ro.rotation / 180);
							m_Scene.GetWorld().sync(ro.GetPhysicalBody());
						}
						this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.UPDATE_RENDER_OBJECT_PROPERTIES(ro)));
						return;
					}
					case STATIC :
					{
						ro.GetPhysicalBody().isStatic = _PropertyValue == "true";
						return;
					}
				}
			}
		}
	}
	
	public function CTRL_AddToSelection<T>(_Class:Class<T>, _Elt:T):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				var roToAdd : RenderObject = cast _Elt;
				var isAlreadySelected:Bool = false;
				for (ro in m_RenderObjectSelection)
				{
					if (ro == roToAdd)
					{
						return;
					}
				}
				m_RenderObjectSelection.push(cast _Elt);
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.ADD_RENDER_OBJECT_TO_SELECTION(cast _Elt)));
			}
			case cast Vector :
			{
				var ptToAdd : Vector = cast _Elt;
				var isAlreadySelected:Bool = false;
				for (pt in m_PhysicPointSelection)
				{
					if (pt == ptToAdd)
					{
						return;
					}
				}
				m_PhysicPointSelection.push(cast ptToAdd);
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.ADD_PHYSIC_POINT_TO_SELECTION(cast ptToAdd)));
			}
			default :
			{
				
			}
		}
	}
	
	public function IsSelected<T>(_Class:Class<T>, _Elt:T):Bool
	{
		var selection : Array<T> = VUE_GetSelection(_Class);
		if (selection != null)
		{
			for (elt in selection)
			{
				if (elt == _Elt)
				{
					return true;
				}
			}
			return false;
		}
		else
		{
			return false;
		}
	}
	
	public function CTRL_RemoveFromSelection<T>(_Class:Class<T>, _Elt:T):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				if (m_RenderObjectSelection.remove(cast _Elt))
				{
					this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.REMOVE_RENDER_OBJECT_FROM_SELECTION(cast _Elt)));
				}
			}
			case cast Vector :
			{
				if (m_PhysicPointSelection.remove(cast _Elt))
				{
					this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.REMOVE_PHYSIC_POINT_FROM_SELECTION(cast _Elt)));
				}
			}
			default : 
			{
				
			}
		}
	}
	
	public function CTRL_SetRenderObjectEditability(_RO:RenderObject, _Editable:Bool):Void
	{
		if (_Editable)
		{
			if(m_EditableRenderObject == _RO)
			{
				return;
			}
			else
			{
				if (m_EditableRenderObject != null)
				{
					this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.QUITE_RENDER_OBJECT_PHYSIC_SHAPE_EDITION(m_EditableRenderObject)));
				}
				m_EditableRenderObject = _RO;
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.PREPARE_RENDER_OBJECT_PHYSIC_SHAPE_EDITION(_RO)));
				return;
			}
			
		}
		else
		{
			if (m_EditableRenderObject == _RO)
			{
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.QUITE_RENDER_OBJECT_PHYSIC_SHAPE_EDITION(_RO)));
				while (m_PhysicPointSelection.length > 0)
				{
					m_PhysicPointSelection.pop();
				}
				m_EditableRenderObject = null;
				return;
			}
		}
	}
	
	public function VUE_GetViewPoint():Point
	{
		return m_Scene.GetViewPoint();
	}
	
	public function CTRL_SetViewPoint(_Point:Point):Void
	{
		m_Scene.SetViewPoint(_Point);
		this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.MOVE_VIEW_POINT(m_Scene.GetViewPoint())));
	}
	
	public function CTRL_SetZoomLevel(_Zoom:Float):Void
	{
		m_Scene.SetZoom(_Zoom);
		this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.CHANGE_ZOOM(m_Scene.GetZoom())));
	}
	
	public function VUE_GetZoomLevel():Float
	{
		return m_Scene.GetZoom();
	}
	
	public function CTRL_MoveElement<T>(_Class:Class<T>, _Elt:T, _X:Float, _Y:Float, _Z:Float):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				var ro : RenderObject = cast _Elt;
				var currentPos : Point = ro.GetPosition();
				currentPos.x += _X/m_Scene.GetZoom();
				currentPos.y += _Y/m_Scene.GetZoom();
				ro.SetPosition(currentPos.x, currentPos.y);
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.MOVE_RENDER_OBJECT(ro)));
			}
			case cast Vector :
			{
				// First we need to find which RenderObject's body is being edited :
				var movingPoint : Vector = cast _Elt;
				for (editableShape in m_EditableRenderObject.GetPhysicalBody().shapes)
				{
					switch(editableShape.type)
					{
						case Shape.POLYGON :
						{
							var ptIte : Vector = editableShape.polygon.verts;
							while (ptIte != null)
							{
								if (ptIte.x == movingPoint.x && ptIte.y == movingPoint.y)
								{
									ptIte.x += _X/m_Scene.GetZoom(); movingPoint.x += _X/m_Scene.GetZoom();
									ptIte.y += _Y/m_Scene.GetZoom(); movingPoint.y += _Y/m_Scene.GetZoom();
									this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.MOVE_PHYSIC_POINT(movingPoint)));
									return;
								}
								ptIte = ptIte.next;
							}
						}
					}
				}
			}
			default : 
			{
				
			}
		}
	}
	
	public function CTRL_AddNewElement<T>(_Class:Class<T>, _NewElt:T):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				cast(_NewElt, RenderObject).x /= m_Scene.GetZoom();
				cast(_NewElt, RenderObject).y /= m_Scene.GetZoom();
				m_Scene.AddRenderObject(cast _NewElt);
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.ADD_NEW_RENDER_OBJECT(cast _NewElt)));
			}
			default :
			{
				
			}
		}
	}
	
	public function CTRL_DeleteElement<T>(_Class:Class<T>, _NewElt:T):Void
	{
		switch(_Class)
		{
			case cast RenderObject :
			{
				m_Scene.RemoveRenderObject(cast _NewElt);
				this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.DELETE_RENDER_OBJECT(cast _NewElt)));
			}
			default :
			{
				
			}
		}
	}
	
	public function CTRL_LoadNewScene():Void
	{
		m_TrickyFR = new FileReference() ;
		m_TrickyFR.browse([new FileFilter("Scene", "*.xml")]) ;
		m_TrickyFR.addEventListener(Event.SELECT, OnFileSelected) ;
	}
	
	private function OnFileSelected(_Evt:Event):Void
	{
		#if debug
			trace("File selected : " + _Evt.target.name);
		#end
		
		m_TrickyFR.addEventListener(Event.COMPLETE , OnSceneFileCompleteEvent) ;
		m_TrickyFR.addEventListener(IOErrorEvent.IO_ERROR, OnError);
		m_TrickyFR.load();
	}
	
	public function CTRL_SaveScene():Void
	{
		m_TrickyFR = new FileReference();
		m_TrickyFR.addEventListener(Event.SELECT, OnSaveFileComplete);
		m_TrickyFR.save(m_Scene.SaveToXML().toString());
	}
	
	private function OnSaveFileComplete(_Evt:Event):Void
	{
		#if debug
			trace("Save SUCCESS");
		#end
	}
	
	private function OnError(_Evt:Event):Void
	{
		#if debug
			trace("ERROR");
		#end
	}
	
	private function OnSceneFileCompleteEvent(_Evt:Event):Void
	{
		#if debug
			trace("Scene file loaded successfully");
		#end
		m_Scene = new Scene();
		m_Scene.LoadXML(new Fast(Xml.parse(_Evt.target.data)));
		RessourceManager.GetInstance().addEventListener(RessourceManagerEvent.PREPARATION_COMPLETE, OnRessourceManagerPreapartionCompleteEvent);
		RessourceManager.GetInstance().PrepareRenderObjects(m_Scene.GetNeededRenderObject());
	}
	
	private function OnRessourceManagerPreapartionCompleteEvent(_Evt:Event):Void
	{
		m_Scene.Instanciate();
		this.dispatchEvent(new LevelEditorModelEvent(E_ModelUpdateType.RELOAD_MODEL));
	}
	
}